import streamlit as st
import akshare as ak
import pywencai
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
from chinese_calendar import is_workday, is_holiday
import plotly.express as px  # 新增Plotly绘图库

# Setting up pandas display options
pd.set_option('display.unicode.ambiguous_as_wide', True)
pd.set_option('display.unicode.east_asian_width', True)
pd.set_option('display.max_rows', None)
pd.set_option('display.max_columns', None)
pd.set_option('display.expand_frame_repr', False)
pd.set_option('display.max_colwidth', 100)


def get_previous_trading_day(date):
    # 获取交易日历数据
    df = ak.tool_trade_date_hist_sina()
    # 将字符串日期转换为date对象
    df['trade_date'] = pd.to_datetime(df['trade_date']).dt.date
    # 处理输入日期格式
    if isinstance(date, datetime):
        target_date = date.date()
    else:
        target_date = date

    # 筛选所有早于目标日期的交易日
    previous_dates = df[df['trade_date'] < target_date]

    if previous_dates.empty:
        raise ValueError("No previous trading day found before the given date")

    # 获取最近的交易日
    previous_trading_day = previous_dates['trade_date'].max()

    return previous_trading_day

def get_last_n_trading_days(target_date, n=30):
    """获取指定日期前n个交易日的列表"""
    df = ak.tool_trade_date_hist_sina()
    df['trade_date'] = pd.to_datetime(df['trade_date']).dt.date
    
    if isinstance(target_date, datetime):
        target_date = target_date.date()
    
    trading_days = df[df['trade_date'] <= target_date]['trade_date'].tolist()
    return trading_days[-n:]

def calculate_poban_rate(date):
    """计算指定日期的破板率"""
    try:
        limit_up_df = get_limit_up_data(date)
        poban_df = get_poban(date)
        
        limit_up_count = len(limit_up_df) if limit_up_df is not None else 0
        poban_count = len(poban_df) if poban_df is not None else 0
        total = limit_up_count + poban_count
        
        if total == 0:
            return 0.0
        return round(poban_count / total * 100, 2)
    except Exception as e:
        st.error(f"获取{date}数据失败: {str(e)}")
        return None

def get_limit_up_data(date):
    param = f"非ST,{date.strftime('%Y%m%d')}涨停"
    df = pywencai.get(query=param, sort_key='成交金额', sort_order='desc', loop=True)
    return df

def get_yesterday_zhangting_data(previous_date , date):
    param = f"非ST,{previous_date.strftime('%Y%m%d')}涨停"
    df = pywencai.get(query=param, sort_key='成交金额', sort_order='desc', loop=True)
    return df

def get_poban(date):
    param = f"非ST,{date.strftime('%Y%m%d')}曾涨停"
    df = pywencai.get(query=param, sort_key='成交金额', sort_order='desc', loop=True)
    return df

def get_limit_down_data(date):
    param = f"非ST,{date.strftime('%Y%m%d')}跌停"
    df = pywencai.get(query=param, sort_key='成交金额', sort_order='desc', loop=True)
    return df

def analyze_continuous_limit_up(df, date):
    # 提取连续涨停天数列和涨停原因类别列
    continuous_days_col = f'连续涨停天数[{date.strftime("%Y%m%d")}]'
    reason_col = f'涨停原因类别[{date.strftime("%Y%m%d")}]'

    # 确保涨停原因类别列存在
    if reason_col not in df.columns:
        df[reason_col] = '未知'

    # 按连续涨停天数降序排序，然后按涨停原因类别排序
    df_sorted = df.sort_values([continuous_days_col, reason_col], ascending=[False, True])

    # 创建结果DataFrame
    result = pd.DataFrame(columns=['连续涨停天数', '股票代码', '股票简称', '涨停原因类别'])

    # 遍历排序后的DataFrame，为每只股票创建一行
    for _, row in df_sorted.iterrows():
        new_row = pd.DataFrame({
            '连续涨停天数': [row[continuous_days_col]],
            '股票代码': [row['股票代码']],
            '股票简称': [row['股票简称']],
            '涨停原因类别': [row[reason_col]]
        })
        result = pd.concat([result, new_row], ignore_index=True)

    return result

def get_concept_counts(df, date):
    concepts = df[f'涨停原因类别[{date.strftime("%Y%m%d")}]'].str.split('+').explode().reset_index(drop=True)
    #concepts = df[f'涨停原因类别[{date.strftime("%Y%m%d")}]'].str.split('+', n=1).str[0].reset_index(drop=True)
    concept_counts = concepts.value_counts().reset_index()
    concept_counts.columns = ['概念', '出现次数']
    return concept_counts

def calculate_promotion_rates(current_df, previous_df, current_date, previous_date):
    """Calculate promotion rates between consecutive days"""
    promotion_data = []

    if previous_df.empty or current_df.empty:
        return pd.DataFrame()

    # 动态列名
    current_days_col = f'连续涨停天数[{current_date.strftime("%Y%m%d")}]'
    previous_days_col = f'连续涨停天数[{previous_date.strftime("%Y%m%d")}]'
    current_reason_col = f'涨停原因类别[{current_date.strftime("%Y%m%d")}]'
    previous_reason_col = f'涨停原因类别[{previous_date.strftime("%Y%m%d")}]'

    # Calculate for each level (from 1 to max consecutive days)
    max_days = max(
        current_df[current_days_col].max() if not current_df.empty else 0,
        previous_df[previous_days_col].max() if not previous_df.empty else 0
    )

    for days in range(1, int(max_days) + 1):
        # 获取前日数据（使用前日列名）
        prev_stocks = previous_df[
            (previous_df[previous_days_col] == days)
        ][['股票代码', '股票简称', previous_days_col, previous_reason_col]].copy()
        prev_stocks.rename(columns={previous_reason_col: '涨停原因'}, inplace=True)

        # 获取当日晋级成功数据（使用当日列名）
        success_stocks = current_df[
            (current_df[current_days_col] == days + 1)
        ][['股票代码', '股票简称', current_days_col, current_reason_col]].copy()
        success_stocks.rename(columns={current_reason_col: '涨停原因'}, inplace=True)

        # 获取失败案例
        failed_stocks = prev_stocks[~prev_stocks['股票代码'].isin(current_df['股票代码'])]

        total = len(prev_stocks)
        success = len(success_stocks)
        rate = f"{success}/{total}={success / total:.0%}"if total > 0 else"N/A"

        promotion_data.append({
            '连板层级': f"{days}进{days + 1}",
            '晋级率': rate,
            '成功案例': success_stocks,
            '失败案例': failed_stocks,
            '总案例数': total
        })

    return pd.DataFrame(promotion_data)

def create_line_chart(data_df, value_col, title, color):
    """创建统一风格的折线图"""
    fig = px.line(data_df, x='交易日期', y=value_col, 
                  title=title,
                  labels={value_col: f'{value_col} (%)'},
                  markers=True,
                  text=value_col)
    
    fig.update_traces(
        line=dict(color=color, width=3),
        marker=dict(size=8, color='crimson'),
        hovertemplate='%{x|%m-%d}',
        texttemplate='%{text:.1f}%',
        textposition='top center',
        # 新增字体样式设置
        textfont=dict(
            family="Arial Black",  # 使用更粗的字体
            size=12,
            color='red',
            weight='bold'  # 字体加粗
        )
    )
    
    fig.update_layout(
        plot_bgcolor='white',
        xaxis=dict(
            tickformat='%m-%d',
            dtick=2*24*60*60*1000,
            showgrid=True,
            gridcolor='lightgray',            
            tickangle=-45
        ),
        yaxis=dict(
            ticksuffix='%',
            dtick=10,
            range=[0, max(data_df[value_col].max() * 1.1, 50)],
            showgrid=True,
            gridcolor='lightgray'
        ),
        hovermode='x unified',
        uniformtext_minsize=8,
        uniformtext_mode='hide'
    )
    
    fig.update_xaxes(showline=True, linewidth=2, linecolor='lightgray')
    fig.update_yaxes(showline=True, linewidth=2, linecolor='lightgray')
    return fig

def app():
    st.title("A股涨停概念分析")
    
    # Date selection
    max_date = datetime.now().date()
    max_date_option = [10, 15, 20, 30]

    with st.sidebar:
        st.header("⚙️ 参数设置")
        selected_date = st.date_input("选择分析日期", max_value=max_date, value=max_date)
        max_date_num = st.selectbox("选择分析天数（天）", max_date_option, index=0)

    if not is_workday(selected_date) or is_holiday(selected_date):
        st.write("所选日期不是A股交易日，请选择其他日期。")
        return

    previous_date = get_previous_trading_day(selected_date)
    st.write(f"分析日期: {selected_date} 和 {previous_date} (前一交易日)")

    # 获取关键数据
    selected_df = get_limit_up_data(selected_date)  # 今日涨停
    previous_df = get_limit_up_data(previous_date)  # 昨日涨停
    poban_df = get_poban(selected_date)  # 今日曾涨停
    yesterdayZhangting = get_yesterday_zhangting_data(previous_date, selected_date)  # 昨日涨停股票

    # 计算关键指标 ----------------------------------------------------------
    # 昨日涨停股票列表
    yesterday_zt_stocks = yesterdayZhangting['股票代码'].tolist() if not yesterdayZhangting.empty else []

    # 今日涨停股票列表
    today_zt_stocks = selected_df['股票代码'].tolist() if not selected_df.empty else []

    # 连板率计算（昨日涨停今日继续涨停）
    lianban_molecule = len(set(yesterday_zt_stocks) & set(today_zt_stocks))
    lianban_denominator = len(yesterday_zt_stocks)
    lianban_rate = (lianban_molecule / lianban_denominator * 100) if lianban_denominator > 0 else 0

    # 破板率计算（曾涨停但未封板）
    poban_stocks = poban_df['股票代码'].tolist() if not poban_df.empty else []
    poban_molecule = len(poban_stocks)
    poban_denominator = len(poban_stocks) + len(today_zt_stocks)
    poban_rate = (poban_molecule / poban_denominator * 100) if poban_denominator > 0 else 0

    # 昨日涨停今日涨幅（获取实际涨幅数据）
    yesterday_today_pct = get_yesterday_zhangting_data(previous_date,selected_date)

    # 计算上涨比例
    yesterday_today_pct['最新涨跌幅'] = pd.to_numeric(yesterday_today_pct['最新涨跌幅'], errors='coerce')

    # Calculate up_count, ignoring NaN values
    up_count = np.sum(yesterday_today_pct['最新涨跌幅'] > 0)

    # Calculate total_count, excluding NaN values
    total_count = yesterday_today_pct['最新涨跌幅'].count()

    # Calculate up_rate
    up_rate = (up_count / total_count * 100) if total_count > 0 else 0

    # 展示关键指标 ----------------------------------------------------------
    st.subheader("情绪指标")
    col1, col2, col3 = st.columns(3)

    # 昨日涨停今日上涨率
    col1.metric(
        "昨日涨停今日上涨率",
        f"{up_count}/{total_count}={up_rate:.1f}%",
        help="昨日涨停的股票中今日上涨的比例"
    )

    # 连板率
    col2.metric(
        "连板晋级率",
        f"{lianban_molecule}/{lianban_denominator}={lianban_rate:.1f}%",
        help="昨日涨停股票今日继续涨停的比例"
    )

    # 破板率
    col3.metric(
        "涨停破板率",
        f"{poban_molecule}/{poban_denominator}={poban_rate:.1f}%",
        help="今日曾触及涨停但收盘未封板的比例"
    )

    # Fetch data for both days
    selected_df = get_limit_up_data(selected_date)
    previous_df = get_limit_up_data(previous_date)

    selected_limit_down_df = get_limit_down_data(selected_date)
    previous_limit_down_df = get_limit_down_data(previous_date)

    # Analyze continuous limit-up for both days
    selected_continuous = analyze_continuous_limit_up(selected_df, selected_date)
    previous_continuous = analyze_continuous_limit_up(previous_df, previous_date)

    # Get concept counts for both days
    selected_concepts = get_concept_counts(selected_df, selected_date)
    previous_concepts = get_concept_counts(previous_df, previous_date)

    # Merge concept counts
    merged_concepts = pd.merge(selected_concepts, previous_concepts, on='概念', how='outer',
                               suffixes=('_selected', '_previous'))
    merged_concepts = merged_concepts.fillna(0)

    # Calculate change
    merged_concepts['变化'] = merged_concepts['出现次数_selected'] - merged_concepts['出现次数_previous']

    # Sort by '出现次数_selected' in descending order
    sorted_concepts = merged_concepts.sort_values('出现次数_selected', ascending=False)

    # Display total limit-up and limit-down stocks for both days
    st.subheader("涨停和跌停股票数量变化")

    # 计算涨停和跌停数量
    selected_total = len(selected_continuous) if selected_continuous is not None else 0
    previous_total = len(previous_continuous) if previous_continuous is not None else 0
    change = selected_total - previous_total

    def get_safe_limit_down_total(limit_down_df):
        """安全地获取跌停股票数量，如果数据为 None 则返回 0"""
        return len(limit_down_df) if limit_down_df is not None else 0

    selected_limit_down_total = len(selected_limit_down_df) if selected_limit_down_df is not None else 0
    previous_limit_down_total = len(previous_limit_down_df) if previous_limit_down_df is not None else 0
    limit_down_change = selected_limit_down_total - previous_limit_down_total

    # 计算涨停环比百分比变化
    if previous_total != 0:
        percentage_change_limit_up = (change / previous_total) * 100
    else:
        percentage_change_limit_up = 0

    # 计算跌停环比百分比变化
    if previous_limit_down_total != 0:
        percentage_change_limit_down = (limit_down_change / previous_limit_down_total) * 100
    else:
        percentage_change_limit_down = 0


    # 显示合并后的涨停和跌停数量
    col1, col2, col3 = st.columns(3)
    col1.metric("上交易日涨跌停数", f"{previous_total} : {previous_limit_down_total}")
    col2.metric("选定日期涨跌停数", f"{selected_total} : {selected_limit_down_total}")
    col3.metric("涨停变化  ：  跌停变化", f"{change:+d} : {limit_down_change:+d}")


    # Display concept changes
    st.subheader("涨停概念变化")
    st.dataframe(sorted_concepts)

    # Create a bar chart for top 10 concepts
    top_10_concepts = sorted_concepts.head(10)

    # ... 前面代码保持不变，直到历史趋势分析部分 ...

    # 历史趋势分析 ----------------------------------------------------------
    st.subheader("历史趋势分析（最近n个交易日）")
    
    # 获取历史交易日数据
    with st.spinner('正在获取历史交易日数据...'):
        trading_dates = get_last_n_trading_days(selected_date, max_date_num)
    
    # 初始化进度条
    progress_bar = st.progress(0)
    total_dates = len(trading_dates)
    
    # 存储结果的列表
    poban_rates = []
    lianban_rates = []
    
    for idx, date in enumerate(trading_dates):
        # 计算破板率
        poban_rate = calculate_poban_rate(date)
        if poban_rate is not None:
            poban_rates.append({'交易日期': date, '破板率': poban_rate})
        
        # 计算连板晋级率
        try:
            previous_date_for_lianban = get_previous_trading_day(date)
            yesterday_zt = get_limit_up_data(previous_date_for_lianban)
            today_zt = get_limit_up_data(date)
            
            if not yesterday_zt.empty and not today_zt.empty:
                yesterday_codes = yesterday_zt['股票代码'].tolist()
                today_codes = today_zt['股票代码'].tolist()
                lianban_count = len(set(yesterday_codes) & set(today_codes))
                total_yesterday = len(yesterday_codes)
                lianban_rate = (lianban_count / total_yesterday * 100) if total_yesterday > 0 else 0
            else:
                lianban_rate = 0
        except Exception as e:
            st.error(f"计算{date}连板率时出错: {str(e)}")
            lianban_rate = 0
        
        lianban_rates.append({'交易日期': date, '连板率': lianban_rate})
        
        # 更新进度条
        progress_bar.progress((idx + 1) / total_dates)
    
    progress_bar.empty()
    
    # 创建两列布局
    col1, col2 = st.columns(2)
    
    # 破板率图表
    with col1:
        if poban_rates:
            poban_df = pd.DataFrame(poban_rates).sort_values('交易日期')
            fig_poban = create_line_chart(poban_df, '破板率', '📉 破板率趋势分析', '#FF6B6B')
            st.plotly_chart(fig_poban, use_container_width=True)
        else:
            st.warning("未能获取破板率历史数据")
    
    # 连板率图表
    with col2:
        if lianban_rates:
            lianban_df = pd.DataFrame(lianban_rates).sort_values('交易日期')
            fig_lianban = create_line_chart(lianban_df, '连板率', '🚀 连板晋级率趋势分析', '#4ECDC4')
            st.plotly_chart(fig_lianban, use_container_width=True)
        else:
            st.warning("未能获取连板率历史数据")

    # Display continuous limit-up analysis
    st.subheader("连续涨停天数分析")
    st.dataframe(selected_continuous)

    st.subheader("连板晋级率分析")
    promotion_rates = calculate_promotion_rates(selected_df, previous_df, selected_date, previous_date)

    if not promotion_rates.empty:
        # 自定义CSS样式
        st.markdown("""
        <style>
            .promotion-card {    
                padding: 1rem;
                margin: 0.5rem 0;
                border: 1px solid #e0e0e0;
                border-radius: 8px;
                background-color: #f8f9fa;
                box-shadow: 0 2px 4px rgba(0,0,0,0.05);       
            }
            .progress {
                height: 8px;
                background: #e0e0e0;
                border-radius: 4px;
                overflow: hidden;
            }
            .progress-bar {
                height: 100%;
                background: #4CAF50;
                transition: width 0.3s ease;
            }
            .success {
                padding: 0.5rem;
                margin: 0.25rem 0;
                background: #e8f5e9;
                border-radius: 4px;
                border-left: 4px solid #28a745;
            }
            .fail {
                padding: 0.5rem;
                margin: 0.25rem 0;
                background: #ffebee;
                border-radius: 4px;
                border-left: 4px solid #dc3545;
            }
            .concept-tag {
                font-size: 0.8em;
                color: #666;
                overflow: hidden;
                text-overflow: ellipsis;
                white-space: nowrap;
            }
        </style>
        """, unsafe_allow_html=True)

        # 转换为字典列表
        promotion_list = promotion_rates.to_dict('records')

        # 每行显示3个卡片
        for i in range(0, len(promotion_list), 3):
            cols = st.columns(3)
            group = promotion_list[i:i+3]

            for j in range(len(group)):
                with cols[j]:
                    item = group[j]

                    with st.expander(f"{item['连板层级']} ▪ 晋级率 {item['晋级率']}", expanded=True):
                        #进度条
                        progress = item['成功案例'].shape[0] / item['总案例数'] if item['总案例数'] > 0 else 0
                        st.markdown(f"""
                              <div class="progress">
                                  <div class="progress-bar" style="width: {progress * 100}%"></div>
                              </div>
                              <div style="text-align: right; color: #666; margin: 0.5rem 0;">
                                  成功率: {progress:.0%}
                              </div>
                           """, unsafe_allow_html=True)
                        
                        # 成功案例
                        if not item['成功案例'].empty:
                            #st.markdown("**成功晋级**")
                            for _, stock in item['成功案例'].iterrows():
                                concept = stock.get(f'涨停原因', '未知')
                                st.markdown(f"""
                                <div class="success">
                                    <div style="display: flex; justify-content: space-between;">
                                        <div>
                                            <span style="font-size:0.9em; font-weight: bold; color:#000;">{stock['股票简称']}</span> 
                                            <span style="font-size:0.8em; color:#b0b0b0;">{stock['股票代码']}</span>
                                        </div>
                                    </div>
                                    <span style="font-size:0.8em; color:#8a8a8a;">{concept}</span>
                                </div>
                                """, unsafe_allow_html=True)

                        # 失败案例
                        if not item['失败案例'].empty:
                            #st.markdown("**晋级失败**")
                            for _, stock in item['失败案例'].iterrows():
                                concept = stock.get(f'涨停原因', '未知')
                                st.markdown(f"""
                                <div class="fail">
                                    <div style="display: flex; justify-content: space-between;">
                                        <div>
                                            <span style="font-size:0.9em; font-weight: bold; color:#000;">{stock['股票简称']}</span> 
                                            <span style="font-size:0.8em; color:#b0b0b0;">{stock['股票代码']}</span>
                                        </div>
                                    </div>
                                    <span style="font-size:0.8em; color:#8a8a8a;">{concept}</span>        
                                </div>
                                """, unsafe_allow_html=True)

                        st.markdown("</div></div>", unsafe_allow_html=True)

if __name__ == "__main__":
    #st.set_page_config(layout="wide")
    app()